package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.dao.SkuInfoMapper;
import com.changgou.search.service.SkuInfoService;
import entity.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @Author: wzw
 * @Date: 2020/12/13 22:09
 * @version: 1.8
 */
@Service
public class SkuInfoServiceImpl implements SkuInfoService {

    //注入(RPC远程调用)Feign
    @Autowired
    private SkuFeign skuFeign;

    //注入(Dao)SkuInfoMapping
    @Autowired
    private SkuInfoMapper skuInfoMapper;

    //注入es模板:ElasticsearchTemplate
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * @author wzw
     * 将数据库数据导入索引库中
     * @Date 22:10 2020/12/13
     * @param 
     * @return void
    **/
    @Override
    public void importSkuInfoToEs() {
        //1.通过Feign调用商品微服务接口
        Result<List<Sku>> result = skuFeign.findSkusByStatus("1");
        //2.将json格式数据转为string格式
        String text = JSON.toJSONString(result.getData());
        //3.将string格式数据转为SkuInfo对象
        List<SkuInfo> skuInfos = JSON.parseArray(text, SkuInfo.class);
        //4.处理动态字段(转换套娃内容)
        //循环所有对象
        for (SkuInfo skuInfo : skuInfos) {
            //获取规格
            String spec = skuInfo.getSpec();
            //将json格式规格的内容封装到map中
            Map<String,Object> map = JSON.parseObject(spec, Map.class);
            //存入准备好的map属性中
            skuInfo.setSpecMap(map);
        }
        //5.将数据存入Es中
        skuInfoMapper.saveAll(skuInfos);
    }

    /**
     * @author wzw
     * 前台检索
     * @Date 10:33 2020/12/14
     * @param searchMap 条件
     * @return java.util.Map<java.lang.String,java.lang.Object>
    **/
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        //1.实现功能:根据关键字查询
        //构建检索条件(后期由多个条件检索,因此我么专门封装一个方法)
        NativeSearchQueryBuilder builder = builderBasicQuery(searchMap);
        //根据关键字检索
        Map<String, Object> resultMap = searchForPage(builder);
        //设置默认字为int最大值
        int size = Integer.MAX_VALUE;

        //4.返回结果集
        // 获取所有分组结果
        Map<String, Object> map = groupList(builder,size);
        //将分组结果map放到resultMap中
        resultMap.putAll(map);

        //返回结果集
        return resultMap;
    }

    // 获取所有分组结果
    private Map<String, Object> groupList(NativeSearchQueryBuilder builder,int size) {

        //1.聚合(分组)查询(别名,es中字段名)
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(size));
        //1.聚合(分组)查询(别名,es中的字段)
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(size));
        //1.聚合(分组)查询(别名,es中字段名)
        builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(size));



        //2.实现功能:查询
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);
        //获取分组结果集
        Aggregations aggregations = aggregatedPage.getAggregations();

        //3.获取skuCategory分组结果集
        List<String> categoryList = getAggregationList(aggregations, "skuCategory");
        //3.处理skuSpec分组结果集
        List<String> list = getAggregationList(aggregations, "skuSpec");
        //将集合数据转入map中
        Map<String, Set<String>> resultMap = pullMap(list);
        //3.处理skuBrand分组结果集
        List<String> brandList = getAggregationList(aggregations, "skuBrand");


        // 封装数据
        Map<String, Object> map = new HashMap<>();
        map.put("brandList", brandList);
        map.put("categoryList", categoryList);
        map.put("specList", resultMap);
        //返回结果集
        return map;
    }

    /**
     * @author wzw
     * 将集合数据转入map中
     * @Date 21:37 2020/12/15
     * @param list
     * @return java.util.Map<java.lang.String,java.util.Set<java.lang.String>>
    **/
    private Map<String, Set<String>> pullMap(List<String> list) {
        //1.创建最终返回结果
        Map<String, Set<String>> map = new HashMap<>();
        //2.处理规格结果集
        for (String spec : list) {
            //2.1将json转成map
            Map<String,String> specMap = JSON.parseObject(spec, Map.class);
            //2.2将map装入set格式
            Set<Map.Entry<String, String>> entries = specMap.entrySet();
            //2.3循环set内容
            for (Map.Entry<String, String> entry : entries) {
                //2.3.1获取规格
                String key = entry.getKey();
                //2.3.2规格选项
                String value = entry.getValue();
                //2.3.3判断 是否有该规格(比如书是没有规格的)
                Set<String> set = map.get(key);
                //2.3.4非空判断(有就不需要覆盖)
                if (set == null) {
                    //2.3.5防止二次new,替换set内容
                    set = new HashSet<>();
                }
                //2.3.6添加内容
                set.add(value);
                //2.3.7添加到最终map<规格,set集合<规格选项>>中
                map.put(key, set);
            }
        }
        //3.返回最终map
        return map;
    }

    /**
     * @author wzw
     * 3.处理分组结果集
     * @Date 19:32 2020/12/15
     * @param aggregations 查询的结果
     * @param skuName 别名
     * @return java.util.List<java.lang.String>
     **/
    private List<String> getAggregationList(Aggregations aggregations, String skuName) {
        //获取单个对象
        StringTerms stringTerms = aggregations.get(skuName);
        //多条件的分词
        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
        //创建List集合
        List<String> list = new ArrayList<>();
        //循环分词后的对象
        for (StringTerms.Bucket bucket : buckets) {
            //将key添加到list集合中
            list.add(bucket.getKeyAsString());
        }
        //返回结果
        return list;
    }



    /**
     * @author wzw
     * 根据条件查询并封装数据
     * @Date 0:03 2020/12/14
     * @param builder
     * @return java.util.Map<java.lang.String,java.lang.Object>
     **/
    private Map<String,Object> searchForPage(NativeSearchQueryBuilder builder) {
        //1.设置高亮条件(对es的name字段包含的关键字进行高亮)
        HighlightBuilder.Field field = new HighlightBuilder.Field("name");
        //2.创建高亮html
        //开始
        field.preTags("<font color='red'>");
        //结束
        field.postTags("</font>");
        //3.封装高亮到条件中
        builder.withHighlightFields(field);
        //4.实现功能:设置高亮到关键字上
        SearchResultMapper searchResultMapper = new SearchResultMapper(){
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                //封装高亮结果集
                ArrayList<T> content = new ArrayList<>();

                //获取高亮结果集(命中数据集合)
                SearchHits hits = response.getHits();
                if (hits != null) {
                    //循环结果集
                    for (SearchHit hit : hits) {
                        //获取单个数据
                        String result = hit.getSourceAsString();
                        //将结果集转成一个个的pojo
                        SkuInfo skuInfo = JSON.parseObject(result, SkuInfo.class);
                        //获取的html高亮的语句
                        HighlightField highlightField = hit.getHighlightFields().get("name");
                        //非空判断
                        if (highlightField != null) {
                            //获取高亮结果集
                            Text[] texts = highlightField.getFragments();
                            //将高亮名称 替换掉 普通的商品name结果
                            skuInfo.setName(texts[0].toString());
                        }
                        //添加高亮对象到list中
                        content.add((T)skuInfo);
                    }
                }
                //将普通的Content进行替换(被设置高亮的list结果集,分页,总高亮数)
                return new AggregatedPageImpl<>(content, pageable, hits.getTotalHits());
            }
        };

        //获取条件对象
        NativeSearchQuery build = builder.build();
        //实现功能:根据根据条件分页查询(条件,pojo字节码,高亮对象)
        AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(build,SkuInfo.class,searchResultMapper);
        //结果集
        List<SkuInfo> rows = page.getContent();

        //总条数
        long totalElements = page.getTotalElements();
        //总页数
        int totalPages = page.getTotalPages();
        //创建Map,封装结果集
        HashMap<String, Object> map = new HashMap<>();
        //结果集
        map.put("rows", rows);
        // 存入总条数
        map.put("totalElements", totalElements);
        // 存入总页数
        map.put("totalPages", totalPages);
        // 存入当前页码
        map.put("pageNum", build.getPageable().getPageNumber() + 1);
        // 存入每页显示的条数
        map.put("pageSize", build.getPageable().getPageSize());

        //返回结果集
        return map;
    }

    /**
     * @author wzw
     * 封装检索条件
     * @Date 0:01 2020/12/14
     * @param searchMap 条件的检索
     * @return org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder
     **/
    private NativeSearchQueryBuilder builderBasicQuery(Map<String, String> searchMap) {
        //封装检索条件:新建对象NativeSearchQueryBuilder添加条件
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //封装过滤条件:
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //判断条件是否为空
        if (searchMap != null) {
            //实现功能:1、根据关键字检索
            // 获取map中的值
            String keywords = searchMap.get("keywords");
            //判断值是否为空
            if (!StringUtils.isEmpty(keywords)) {
                //不为空:封装条件,采用es的方式:query(match)
                //根据es中的字段name来查询
                builder.withQuery(QueryBuilders.matchQuery("name", keywords));
            }

            //实现功能:2.根据商品分类过来
            //获取map中的值
            String category = searchMap.get("category");
            //空值判断
            if (!StringUtils.isEmpty(category)) {
                //为空:查询数据(根据es中的字段categoryName来查询,条件)
                boolQuery.must(QueryBuilders.matchQuery("categoryName",category));
            }

            //实现功能:3、根据品牌过滤
            String brand = searchMap.get("brand");
            //空值判断
            if (!StringUtils.isEmpty(brand)) {
                //为空:查询数据(根据es中的字段categoryName来查询,条件)
                boolQuery.must(QueryBuilders.matchQuery("brandName",brand));
            }

            //实现功能:4.根据规格过滤
            // 获取条件(规格的value有很多)
            Set<String> keys = searchMap.keySet();
            //循环key
            for (String key : keys) {
                //判断前缀:以spec_开头,都是规格条件(所以在前台发请求的时候要特别注意)
                if (key.startsWith("spec_")){
                    //替换
                    String value = searchMap.get(key).replace("\\", "");

                    //es中规格字段:specMap.Xxx.keyword ,条件
                    // substring截取掉spec_
                    boolQuery.must(QueryBuilders.matchQuery("specMap."+key.substring(5)+".keyword", value));
                }
            }

            //实现功能:5、价格区间段过滤
            //价格:区间的格式:[500-1000] or xxx以上
            String price = searchMap.get("price");
            //空判断
            if (!StringUtils.isEmpty(price)){
                //分割出[500,1000]
                String[] priceArray = price.split("-");
                //范围查询[min,max]
                //>= 大于等于 或 多少以上
                boolQuery.must(QueryBuilders.rangeQuery("price").gte(priceArray[0]));
                //确保范围
                if (priceArray.length == 2) {
                    //<= 小于等于
                    boolQuery.must(QueryBuilders.rangeQuery("price").lte(priceArray[1]));
                }
            }

            //实现功能:6、结果排序
            //排序规则(名字自定义)
            String sortRule = searchMap.get("sortRule");
            // 排序字段
            String sortField = searchMap.get("sortField");
            //空判断
            if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
                //排序withSort(字段.排序规则)
                builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
            }
        }
        //添加过滤条件
        builder.withFilter(boolQuery);
        //=========分页查询开始============
        //获取前端传过来的页码
        String pageNum = searchMap.get("pageNum");
        //非空判断
        if (StringUtils.isEmpty(pageNum)) {
            //默认值:第一页(不能为空)
            pageNum = "1";
        }
        //起始行 = (当前页码 - 1 ) * 每页显示的条数
        int page = Integer.parseInt(pageNum) - 1;
        //设置默认值:每页显示的条数
        int size = 40;
        //设置分页条件
        PageRequest pageable = PageRequest.of(page, size);
        //实现功能:分页查询
        builder.withPageable(pageable);
        //=========分页查询结束============


        //返回封装条件的对象
        return builder;
    }
}
